import os
from time import sleep
from typing import Generator

import requests
from lxml import etree
from redis import Redis

from pipelines import DoubanMysqlPipeline, DoubanMongoPipeline
from xunter_utils.xunter_requests import ChromeClient

"""
爬虫功能类
服务端程序  部署
从redis列表中获取任务列表并且执行任务
"""

#创建一个电影数据存储类来保存得到的电影数据(类似于c中的结构体)  可拓展性高  数据结构类
class DoubanItem:
    detail_url:str
    titles:str
    name:str
    classes:str
    rating_num:float
    appraise_num:int
    quote:str
    img_title:str
    img_src:str

    def __str__(self):
        return '%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n\n' % (
            '详情地址',self.detail_url,
            '标题',self.titles,
            '导演和演员',self.name,
            '类型',self.classes,
            '评分',self.rating_num,
            '评价人数',self.appraise_num,
            '简介',self.quote,
            '图片地址',self.img_src,
        )

class DoubanTop(ChromeClient):          #功能类
    def __init__(self, download_path='Download'):
        """
        初始化
        :param download_path:文件保存地址
        """
        self.host="https://movie.douban.com"
        self.download_path=download_path

        if not os.path.isdir(download_path):
            os.mkdir(download_path)

    #得到电影列表
    def get_list(self,page: int=1) -> Generator:
        """
        每页电影的列表数据
        :param page:
        :return:
        """
        start = (page - 1) * 25
        res = self.send_request(f'{self.host}/top250?start={start}&filter=')
        return self.parse(res)

    def parse(self,response:requests.Response) -> Generator:
        #数据解析部分 html数据， xpath来完成数据解析
        html = etree.HTML(response.text)     #把字符串数据变为html树形结构对象

        grid_view = html.xpath('//ol[@class="grid_view"]/li')  # 获取任意位置class为grid_view的ol标签里的所有li

        # res_data = []  #保存电影数据  通过yield生成器优化就不需要将数据保存到列表中 而是调用方法时取一个调一个
        # 获取每个li结构对象
        for grid in grid_view:
            item = DoubanItem()
            item.detail_url = grid.xpath('.//div[@class="hd"]/a/@href')[0]  # 自己通过分析需要获取信息的标签和标签的上级标签写的xpath

            titles = grid.xpath(
                './/div[@class="hd"]/a/span/text()')  # 获取标题  注意这里要找到数据所在的具体标签  数据所在标签不给下标的话会返回一个列表 包含所有为该标签的数据
            item.titles = ''.join(titles)
            item.name = grid.xpath('.//div[@class="bd"]/p/br/preceding-sibling::text()')[0].strip()  #strip()删除两边的空格# 导演和演员信息 这里以br为分界线向上取‘哥哥’的数据

            item.classes = grid.xpath('.//div[@class="bd"]/p/br/following-sibling::text()')[0].strip()  # 时间产地分类信息  向下取‘弟弟’信息

            item.rating_num = grid.xpath('.//span[@class="rating_num"]/text()')[0]

            item.appraise_num = int(grid.xpath('.//div[@class="star"]/span[last()]/text()')[0].replace('人评价',''))

            # item.quote = grid.xpath('.//span[@class="inq"]/text()')[0]
            quote = grid.xpath('.//span[@class="inq"]/text()')
            item.quote = quote[0] if quote else ''
            # if quote:
            #     item.quote = quote[0]
            # else:
            #     item.quote = ''

            item.img_title = grid.xpath('.//div[@class="pic"]//img/@alt')[0]  # 获取class为pic里的img标签里面的alt属性值  图片名称

            item.img_src = grid.xpath('.//div[@class="pic"]//img/@src')[0]  # 图片地址

        #优化   通过yield生成器优化就不需要将数据保存到列表中 而是调用方法时取一个调一个
            yield item

    def save_txt(self,file_name:str,data:DoubanItem):
        """
        保存豆瓣电影数据对象到文本中
        :param file_name: 文件名
        :param data: 豆瓣电影数据对象
        :return:
        """
        with open(os.path.join(
            self.download_path,
            file_name
        ),'a',encoding='utf-8') as f:
            f.write(str(data))

    def save_img(self,file_name:str,data:bytes):
        """
        保存图片
        :param file_name:图片地址
        :param data: 图片数据
        :return:
        """
        with open(os.path.join(
            self.download_path,
            file_name
        ),'wb',) as f:
            f.write(data)

    def set_redis_task(self,redis_client:Redis,page:int=1):
        """
        写入redis任务
        得到每一页的地址存到redis列表中
        :param redis_client:redis客户端  连接对象
        :param page:页数
        :return:
        """
        start = (page - 1) * 25
        url = f'{self.host}/top250?start={start}&filter='   #得到任务地址
        redis_client.lpush('douban_urls',url)    #将任务地址写到redis列表中  键值对

if __name__ == '__main__':
    #创建redis连接对象  redis用于断点续爬
    redis_client = Redis(host='127.0.0.1',port=6379,decode_responses=True)
    client=DoubanTop()  #爬虫类
    # pipline = DoubanExcelPipeLine(
    #     os.path.join(
    #         client.download_path,
    #         '豆瓣TOP250.xlsx'
    #     )
    # )

    # pipline = DoubanMysqlPipeline()     #使用MySQL存储数据
    pipline = DoubanMongoPipeline()    #使用MySQL存储数据

    # for p in range(1,12):
    #     print(f'开始抓取第{p}页')
    #     for i in client.get_list(p):  # 这样子我们调用的方法回一次性得到所有的电影信息，但是我们去保存看到是一个一个的写入到文件中去的
    #         print(i.titles)
    #         client.save_txt('top.txt', i)  # 通过yield生成器优化 在调用方法时不会全部获取数据而是用一个取一个
    #         img_res = client.send_request(i.img_src)
    #         client.save_img(i.img_title + '.jpg', img_res.content)

    # for p in range(1,11):
    #     print(f'开始抓取第{p}页')
    #     for i in client.get_list(p):
    #         pipline.process_item(i)
    #

    #redis断点续爬
    # #生成任务：
    # for i in range(1,11):
    #     client.set_redis_task(redis_client,i)

    # #消费任务  取任务  执行任务
    # over_movie_fp = open('over_movie.txt','a+',encoding='utf-8')
    # over_movie_fp.seek(0)   #设置光标位置
    # over_movie = over_movie_fp.read().split('\n')   #读取文件
    # for url in redis_client.lrange('douban_urls',0,-1):
    #    print(url)
    #    res = client.send_request(url)   #请求抓取数据
    #    for item in client.parse(res):   #从解析函数中得到返回的数据
    #        if item.img_title not in over_movie: #如果我们没有抓取这部电影，那么我们就进行储存
    #            pipline.process_item(item)   #数据存储
    #            over_movie_fp.write(item.img_title+'\n') #将储存的电影写入文件中
    #            over_movie_fp.flush()    #文件刷新
    #    redis_client.lrem('douban_urls',0,url)   #删除完成的请求地址

    # pipline.close_spider()
    # redis_client.close()


    while True:
        for url in redis_client.lrange('douban_urls',0,-1):
            print(url)
            redis_client.lrem('douban_urls', 0, url)  # 删除完成的请求地址
        # sleep(3)



